package com.demo.base.web.webmvc;

import com.demo.base.exception.ServerErrorException;
import com.demo.base.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 仅处理标记有@link @RequestBody且请求为 @link MediaType.APPLICATION_FORM_URLENCODED 请求
 * <p>
 * 解决问题：
 * 当Controller接口方法参数添加@RequestBody注解时无法解析Form方式提交的表单
 *
 * @link http://stackoverflow.com/questions/16449141/spring-config-and-the-requestbody-annotation-with-x-www-form-urlencoded-data_source
 * @link http://stackoverflow.com/questions/4339207/http-post-with-request-content-type-form-not-working-in-spring-config-3/31083802#31083802
 * <p>
 * Created by ethan on 2017/2/8.
 */
public class UrlEncodedFormRequestBodyHttpMessageConverter implements HttpMessageConverter<Object> {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        return getSupportedMediaTypes().contains(mediaType);
    }

    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return new ArrayList<MediaType>() {{
            add(MediaType.APPLICATION_FORM_URLENCODED);
            add(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8));
        }};
    }

    @Override
    public Object read(Class<?> aClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        MediaType contentType = inputMessage.getHeaders().getContentType();
        Charset charset = (contentType.getCharset() != null ? contentType.getCharset() : DEFAULT_CHARSET);
        String body = StreamUtils.copyToString(inputMessage.getBody(), charset);

        String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>(pairs.length);
        for (String pair : pairs) {
            int idx = pair.indexOf('=');
            if (idx == -1) {
                map.add(URLDecoder.decode(pair, charset.name()), null);
            } else {
                String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
                String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
                map.add(name, value);
            }
        }
        JSONObject params = new JSONObject();
        for (Map.Entry<String, List<String>> item : map.entrySet()) {
            List<String> values = item.getValue();
            if (values.size() > 1) {
                params.put(item.getKey(), values);
            } else if (values.size() == 1) {
                params.put(item.getKey(), values.get(0));
            } else {
                params.put(item.getKey(), null);
            }
        }
        return params.toJavaObject(aClass);
    }

    @Override
    public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        throw new ServerErrorException("未实现，此类仅用于接收表单，不可用于输出类型");
    }

}
